
"""
知识分割模块

"""
import os
import re

os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

class BaseSegmenter():
    pass

class KnowledgeSegmenter:

    def split_text(self, text, length):
        # 正则表达式匹配标点符号
        punctuation_pattern = re.compile(r'[,.!?/#$%^&*()_+{}~。|:"！？｡＂＃＄％＆＇（）＊＋，－／：；＜＝＞＠［＼］＾＿｀｛｜｝～]+')
        words = text.split()
        result = []
        current_chunk = []
        current_length = 0

        for word in words:
            # 如果当前单词加上标点符号的总长度超过限制，则先添加当前块
            if current_length + len(word) + len(punctuation_pattern.findall(word)) > length:
                result.append(''.join(current_chunk))
                current_chunk = []
                current_length = 0

            # 添加单词到当前块
            current_chunk.append(word)
            # 计算当前块的长度，包括标点符号
            current_length += len(word) + len(punctuation_pattern.findall(word))

        # 添加最后一个块
        if current_chunk:
            result.append(''.join(current_chunk))

        return result

    def seg(self, text, token=61):
        # 开始分块
        results = self.split_text(text, token)

        wait_sen_texts = []
        for i in range(1, len(results)):
            tmp_text = f"{results[i - 1]} {results[i]}"
            wait_sen_texts.append(tmp_text)

        return wait_sen_texts


if __name__ == '__main__':

    text="""

RAG涉及的内容其实广泛，包括Embedding、分词分块、检索召回（相似度匹配）、chat系统、ReAct和Prompt优化等，最后还有与LLM的交互，整个过程技术复杂度很高。如果你用的LLM非常好，反而大模型这一块是你最不需要关心的。而这些环节里面我们每个都没达到1（比如0.9、0.7...），那么最终的结果可能是这些小数点的乘积。如果我们每个环节都可以做到>1.0，那么最终的结果会比上一个结果高出很多。

今天我们来聊聊分块，很重要的一个环节（没有哪个环节不重要），但它也许是我们容易做到高质量的一个环节。

什么是分块？
在构建RAG这类基于LLM的应用程序中，分块（chunking）是将大块文本分解成小段的过程。当我们使用LLM embedding内容时，这是一项必要的技术，可以帮助我们优化从向量数据库被召回的内容的准确性。在本文中，我们将探讨它是否以及如何帮助提高RAG应用程序的效率和准确性。

IMG_257

Pinecone是领先的向量数据库供应商

在向量数据库（如：Pinecone）中索引的任何内容都需要首先Embedding（可以看我上一篇文章大模型应用中大部分人真正需要去关心的核心——Embedding）。分块的主要原因是尽量减少我们Embedding内容的噪音。

例如，在语义搜索中，我们索引一个文档语料库，每个文档包含一个特定主题的有价值的信息。通过使用有效的分块策略，我们可以确保搜索结果准确地捕获用户查询的需求本质。如果我们的块太小或太大，可能会导致不精确的搜索结果或错过展示相关内容的机会。根据经验，如果文本块尽量是语义独立的，也就是没有对上下文很强的依赖，这样子对语言模型来说是最易于理解的。因此，为语料库中的文档找到最佳块大小对于确保搜索结果的准确性和相关性至关重要。

另一个例子是会话Agent。我们使用embedding的块为基于知识库的会话agent构建上下文，该知识库将代理置于可信信息中。在这种情况下，对分块策略做出正确的选择很重要，原因有两个：

·首先，它将决定上下文是否与我们的prompt相关。

·其次，考虑到我们可以为每个请求发送的tokens数量的限制，它将决定我们是否能够在将检索到的文本合并到prompt中发送到大模型(如OpenAI)。

在某些情况下，比如使用具有32k上下文窗口的GPT-4时，拟合块可能不是问题。尽管如此，当我们使用非常大的块时，我们需要注意，因为这可能会对我们从向量数据库获得的结果的相关性产生不利影响。

在这篇文章中，我们将探讨几种分块方法，并讨论在选择分块大小和方法时应该考虑的权衡。最后，我们将给出一些建议，以确定适合您的应用程序的最佳块大小和方法。

Embedding长短内容
当我们在嵌入内容（也就是embedding）时，我们可以根据内容是短（如句子）还是长（如段落或整个文档）来预测不同的行为。

当嵌入一个句子时，生成的向量集中在句子的特定含义上。当与其他句子Embedding进行比较时，自然会在这个层次上进行比较。这也意味着Embedding可能会错过在段落或文档中找到的更广泛的上下文信息。

当嵌入一个完整的段落或文档时，Embedding过程既要考虑整个上下文，也要考虑文本中句子和短语之间的关系。这可以产生更全面的向量表示，从而捕获文本的更广泛的含义和主题。另一方面，较大的输入文本大小可能会引入噪声或淡化单个句子或短语的重要性，从而在查询索引时更难以找到精确的匹配。

查询的长度也会影响Embeddings之间的关系。较短的查询，如单个句子或短语，将专注于细节，可能更适合与句子级Embedding进行匹配。跨越多个句子或段落的较长的查询可能更适合段落或文档级别的Embedding，因为它可能会寻找更广泛的上下文或主题。

索引也可能是非同质的，并且包含“不同”大小的块的Embedding。这可能会在查询结果相关性方面带来挑战，但也可能产生一些积极的后果。一方面，由于长内容和短内容的语义表示不一致，查询结果的相关性可能会波动。另一方面，非同构索引可能捕获更大范围的上下文和信息，因为不同的块大小表示文本中的不同粒度级别。这可以更灵活地容纳不同类型的查询。

分块需要考虑的因素
在确定最佳分块策略时，有几个因素会对我们的选择起到至关重要的影响。以下是一些事实我们需要首先记在心里：

1.被索引内容的性质是什么?这可能差别会很大，是处理较长的文档(如文章或书籍)，还是处理较短的内容(如微博或即时消息)？答案将决定哪种模型更适合您的目标，从而决定应用哪种分块策略。

2.您使用的是哪种Embedding模型，它在多大的块大小上表现最佳？例如，sentence-transformer~[1]~模型在单个句子上工作得很好，但像text- embedt-ada -002~[2]~这样的模型在包含256或512个tokens的块上表现得更好。

3.你对用户查询的长度和复杂性有什么期望？用户输入的问题文本是简短而具体的还是冗长而复杂的？这也直接影响到我们选择分组内容的方式，以便在嵌入查询和嵌入文本块之间有更紧密的相关性。

4.如何在您的特定应用程序中使用检索结果？例如，它们是否用于语义搜索、问答、摘要或其他目的？例如，和你底层连接的LLM是有直接关系的，LLM的tokens限制会让你不得不考虑分块的大小。

没有最好的分块策略，只有适合的分块策略，为了确保查询结果更加准确，有时候我们甚至需要选择性地使用几种不同的策略。

分块的方法
分块有不同的方法，每种方法都可能适用于不同的情况。通过检查每种方法的优点和缺点，我们的目标是确定应用它们的正确场景。

固定大小分块
这是最常见、最直接的分块方法：


请输入问题，回车发送

    """
    seg_model=KnowledgeSegmenter()
    ss=seg_model.seg(text)
    for s in ss:
        print(s)
        print("===")